package com.bthdung.studios.lesson12;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;
import android.util.Log;
import com.bthdung.studios.R;
import com.bthdung.studios.lesson12.framework.Texture2D;
import com.bthdung.studios.lesson12.framework.Vector3;
import com.bthdung.studios.lesson12.framework.VertexPositionTextureNormal;
import com.bthdung.studios.lesson12.framework.buffers.IndexBuffer;
import com.bthdung.studios.lesson12.framework.buffers.VertexBuffer;
import com.bthdung.studios.lesson12.framework.program.Effect;
import com.bthdung.studios.lesson12.framework.program.Program;
import com.bthdung.studios.lesson12.glUtil.AndUtil;
import com.bthdung.studios.lesson12.glUtil.Lab;
import com.bthdung.studios.lesson12.glUtil.OptimizedLab;

@SuppressLint("NewApi")
public class MyRenderer implements Renderer
{
	Program program;

	float[] world = new float[16];
	float[] view = new float[16];
	float[] projection = new float[16];

	final float[] identity = new float[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };

	Context context;

	// Positions of attributes in shader 
	int vPosition, vNormal, vTexCoord;
	
	// Position of sampler uniform
	int texSampler;

	// Eye position = zoom * eyeDir 
	Vector3 eyeDir = new Vector3(0, 15, 20);
	float zoom = 1f;

	// Terrain rotation and velocity
	public float vel = 0;
	float angle = 0;

	// Terrain buffers
	VertexBuffer vertexBuffer = null;
	IndexBuffer indexBuffer = null;

	Texture2D terrainTex;

	// For frame rate calculation	
	long start = 0;
	int frames = 0;

	public MyRenderer(Context context)
	{
		this.context = context;
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config)
	{
		GLES20.glClearColor(0, 0.63f, 0.9f, 1);

		// Create terrain vetrex and index buffer
		initGeometry();

		String vShader = AndUtil.readTextResource(context, R.raw.v_shader);
		String fShader = AndUtil.readTextResource(context, R.raw.f_shader);
		program = new Program(vShader, fShader);

		Bitmap tex = AndUtil.loadBitmap(context, R.drawable.texture, true);
		terrainTex = new Texture2D(tex);

		// Get attributes and sampler positions
		Effect effect = program.glGetEffect();
		vPosition = effect.glGetAttributePosition("vPosition");
		vNormal = effect.glGetAttributePosition("vNormal");
		vTexCoord = effect.glGetAttributePosition("vTexCoord");

		texSampler = effect.glGetUniformPosition("texSampler");
	}

	// Creates the index and vertex buffers only using arrays, fast and less memory usage 
	void initGeometry()
	{
		// Loading the heightmap, we must make sure that it won't be scaled when loaded.
		// Max value of short = 32767 = max number of vertices we can have in a single vertex buffer
		// Sorry but no unsigned short in java!
		Bitmap heightMap = AndUtil.loadBitmap(context, R.drawable.heightmap, false);


		int w = heightMap.getWidth();
		int h = heightMap.getHeight();

		short[] indices = Lab.getIndexes(w, h);
		float[] verts = OptimizedLab.genGrid(w, h, 0.20f, heightMap);
		OptimizedLab.calcNormals(indices, verts);

		vertexBuffer = new VertexBuffer(verts);
		indexBuffer = new IndexBuffer(indices);
	}

	// Creates the same index and vertex buffers using VertexPositionTextureNormal class, slow and uses more memory
	void initGeometry2()
	{
		Bitmap heightMap = AndUtil.loadBitmap(context, R.drawable.heightmapgamearea, false);

		int w = heightMap.getWidth();
		int h = heightMap.getHeight();

		short[] indices = Lab.getIndexes(w, h);

		VertexPositionTextureNormal[] verts = Lab.genGridVerts(w, h, 0.20f, heightMap);
		Lab.calcVertsNormals(indices, verts);

		vertexBuffer = new VertexBuffer(verts);
		indexBuffer = new IndexBuffer(indices);
	}

	void update()
	{ 
		angle += vel;
		vel *= 0.99;

		Matrix.rotateM(world, 0, identity, 0, angle, 0, 1, 0);

		program.glGetEffect().glSetUniformMatrix4fv("World", world);
		program.glGetEffect().glSetUniformMatrix4fv("View", view);
		program.glGetEffect().glSetUniformMatrix4fv("Projection", projection);
	}

	@Override
	public void onDrawFrame(GL10 gl)
	{
		if (frames == 0)
		{
			start = System.currentTimeMillis();
		}
		else
		{
			long time = System.currentTimeMillis();
			if (time - start > 1000)
			{
				Log.d("FPS", "" + (frames - 1));
				frames = 1;
				start = time;
			}
		}

		GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

		update();

		vertexBuffer.glBind();
		indexBuffer.glBind();

		VertexPositionTextureNormal.glBindBuffersToAttribs(vPosition, vNormal, vTexCoord);

		terrainTex.glActivate(GLES20.GL_TEXTURE0);
		GLES20.glUniform1i(texSampler, 0);

		GLES20.glDrawElements(GLES20.GL_TRIANGLES, indexBuffer.glGetCapacity(), GLES20.GL_UNSIGNED_SHORT, 0);

		frames++;
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height)
	{
		GLES20.glViewport(0, 0, width, height);

		float ratio = (float) width / (float) height;

		Matrix.frustumM(projection, 0, -ratio, ratio, -1, 1, 1, 100);
		Matrix.setLookAtM(view, 0, eyeDir.x, eyeDir.y, eyeDir.z, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

		program.glUseProgram();

		GLES20.glEnable(GLES20.GL_DEPTH_TEST);
		GLES20.glEnable(GLES20.GL_CULL_FACE);
		GLES20.glCullFace(GLES20.GL_BACK);
	}

	public void zoom(float amount)
	{
		zoom += amount;
		zoom = AndUtil.clamp(zoom, 0.5689455f, 3.2176557f);

		Vector3 newEye = new Vector3(zoom * eyeDir.x, zoom * eyeDir.y, zoom * eyeDir.z);

		Matrix.setLookAtM(view, 0, newEye.x, newEye.y, newEye.z, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
		program.glGetEffect().glSetUniformMatrix4fv("View", view);
	}

}
